package com.leigq.www.mybatisplusstudy.web.config.mvc;

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * String 类型参数转为 Enum 类型，适用 Get请求，Post表单请求的普通请求，不适用 POST json 请求
 * <pre>
 *     IEnum 是 MybatisPlus 中的自定义枚举接口，也可以自己写一个
 *     需要转换的枚举实现 com.baomidou.mybatisplus.annotation.IEnum 接口 或者 在需要转换的属性上加 @com.baomidou.mybatisplus.annotation.EnumValue 注解
 * </pre>
 * @author leiguoqing
 */
@SuppressWarnings(value = {"all"})
@Slf4j
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
    /**
     * The constant CONVERTER_MAP.
     */
    private static final Map<Class<?>, Converter<String, ? extends Enum<?>>> CONVERTER_MAP = new ConcurrentHashMap<>();

    /**
     * The constant TABLE_METHOD_OF_ENUM_TYPES.
     */
    private static final Map<Class<?>, Method> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();

    /**
     * Gets converter.
     *
     * @param <T>        the type parameter
     * @param targetType the target type
     * @return the converter
     */
    @Override
    @SuppressWarnings("unchecked cast")
    public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
        // 缓存转换器
        Converter<String, T> converter = (Converter<String, T>) CONVERTER_MAP.get(targetType);
        if (converter == null) {
            converter = new StringToEnumConverter<>(targetType);
            CONVERTER_MAP.put(targetType, converter);
        }
        return converter;
    }

    /**
     * The type String to enum converter.
     *
     * @param <T> the type parameter
     */
    static class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> {

        /**
         * The Enum map.
         */
        private final Map<String, T> enumMap = new ConcurrentHashMap<>();

        /**
         * Instantiates a new String to enum converter.
         *
         * @param enumType the enum type
         */
        StringToEnumConverter(Class<T> enumType) {
            Method method = getMethod(enumType);
            T[] enums = enumType.getEnumConstants();

            // 将值与枚举对象对应并缓存
            for (T e : enums) {
                try {
                    enumMap.put(method.invoke(e).toString(), e);
                } catch (IllegalAccessException | InvocationTargetException ex) {
                    log.error("获取枚举值错误!!! ", ex);
                }
            }
        }


        /**
         * Convert t.
         *
         * @param source the source
         * @return the t
         */
        @Override
        public T convert(String source) {
            // 获取
            T t = enumMap.get(source);
            if (t == null) {
                throw new IllegalArgumentException("该字符串找不到对应的枚举对象 字符串:" + source);
            }
            return t;
        }
    }

    /**
     * Gets method.
     *
     * @param <T>      the type parameter
     * @param enumType the enum type
     * @return the method
     */
    public static <T> Method getMethod(Class<T> enumType) {
        Method method;
        // 找到取值的方法
        if (IEnum.class.isAssignableFrom(enumType)) {
            try {
                method = enumType.getMethod("getValue");
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException(String.format("类:%s 找不到 getValue方法", enumType.getName()));
            }
        } else {
            method = TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(enumType, k -> {
                Field field = dealEnumType(enumType)
                        .orElseThrow(
                                () -> new IllegalArgumentException(String.format("类:%s 找不到 EnumValue注解", enumType.getName()))
                        );
                return ReflectionUtils.findMethod(enumType, "get" + StringUtils.capitalize(field.getName()));
            });
        }
        return method;
    }

    /**
     * Deal enum type optional.
     *
     * @param clazz the clazz
     * @return the optional
     */
    private static Optional<Field> dealEnumType(Class<?> clazz) {
        return clazz.isEnum() ?
                Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst() : Optional.empty();
    }
}
